system_prompt = """
You run in a loop of Thought, Action, Observation, Answer.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you.
Observation will be the result of running those actions.
Answer will be the result of analysing the Observation

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

fetch_real_time_info:
e.g. fetch_real_time_info: Django
Returns a real info from searching SerperAPI

Always look things up on fetch_real_time_info if you have the opportunity to do so.

Example session:

Question: What is the capital of China?
Thought: I should look up on SerperAPI
Action: fetch_real_time_info: What is the capital of China?
PAUSE 

You will be called again with this:

Observation: China is a country. The capital is Beijing.
Thought: I think I have found the answer
Action: Beijing.
You should then call the appropriate action and determine the answer from the result

You then output:

Answer: The capital of China is Beijing

Example session

Question: What is the mass of Earth times 2?
Thought: I need to find the mass of Earth on fetch_real_time_info
Action: fetch_real_time_info : mass of earth
PAUSE

You will be called again with this: 

Observation: mass of earth is 1,1944×10e25

Thought: I need to multiply this by 2
Action: calculate: 5.972e24 * 2
PAUSE

You will be called again with this: 

Observation: 1,1944×10e25

If you have the answer, output it as the Answer.

Answer: The mass of Earth times 2 is 1,1944×10e25.

Now it's your turn:
""".strip()

import requests
import json
import os
from dotenv import load_dotenv, find_dotenv # 导入 find_dotenv 帮助定位

# 加载 .env 文件中的环境变量 (增强调试)
load_dotenv(dotenv_path=find_dotenv(usecwd=True), verbose=True, override=True)

# 从环境变量加载 API 密钥和基础 URL
api_key = os.getenv("OPENAI_API_KEY")
api_base = os.getenv("OPENAI_API_BASE")
serpapi_api_key = os.getenv("SERPAPI_API_KEY")
model = os.getenv("DEFAULT_MODEL")
os.environ["OPENAI_API_KEY"] = api_key
os.environ["OPENAI_API_BASE"] = api_base
os.environ["SERPAPI_API_KEY"] = serpapi_api_key


def fetch_real_time_info(query):
    # API参数
    params = {
        'api_key': serpapi_api_key,  # 使用您自己的API密钥
        'q': query,  # 查询参数，表示要搜索的问题。
        'num': 1  # 返回结果的数量设为1，API将返回一个相关的搜索结果。
    }

    # 发起GET请求到Serper API
    api_result = requests.get('https://google.serper.dev/search', params)

    # 解析返回的JSON数据
    search_data = api_result.json()

    # 提取并返回查询到的信息
    if search_data["organic"]:
        return search_data["organic"][0]["snippet"]
    else:
        return "没有找到相关结果。"

def calculate(operation: str) -> float:
    return eval(operation)

available_actions = {
    "fetch_real_time_info": fetch_real_time_info,
    "calculate": calculate,
}

import openai
import re
import httpx

from openai import OpenAI

class ChatBot:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        client = OpenAI()
        completion = client.chat.completions.create(model="gpt-4o", messages=self.messages)
        return completion.choices[0].message.content

# (\w+) 是一个捕获组，匹配一个或多个字母数字字符（包括下划线）。这部分用于捕获命令中指定的动作名称
# (.*) 是另一个捕获组，它匹配冒号之后的任意字符，直到字符串结束。这部分用于捕获命令的参数。
action_re = re.compile('^Action: (\w+): (.*)$')
# match = action_re.match("Action: fetch_real_time_info: mass of earth")
# if match:
#     print(match.group(1))  # 'fetch_real_time_info'
#     print(match.group(2))  # 'mass of earth'

action_re = re.compile('^Action: (\w+): (.*)$')

def AgentExecutor(question, max_turns=5):
    i = 0
    bot = ChatBot(system_prompt)
    # 通过 next_prompt 标识每一个子任务的阶段性输入
    next_prompt = question
    while i < max_turns:
        i += 1
        # 这里调用的就是 ChatBot 类的 __call__ 方法
        result = bot(next_prompt)
        print(f"result:{result}")
        # 在这里通过正则判断是否到了需要调用函数的Action阶段
        actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
        if actions:
            # 提取调用的工具名和工具所需的入参
            action, action_input = actions[0].groups()
            if action not in available_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(f"running: {action} {action_input}")
            observation = available_actions[action](action_input)
            print(f"Observation: {observation}")
            next_prompt = "Observation: {}".format(observation)
        else:
            return bot.messages

AgentExecutor("世界上最长的河流，与中国最长的河流，它们之间的差值是多少？")
